using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace QLNet
{
   public class FixedLeg : Swap
   {
      public enum Type { Receiver = -1, Payer = 1 };

      private Type type_;
      private double fixedRate_;
      private double spread_;
      private double nominal_;

      private Schedule fixedSchedule_;
      public Schedule fixedSchedule() { return fixedSchedule_; }

      private DayCounter fixedDayCount_;
      public DayCounter fixedDayCount() { return fixedDayCount_; }

      private BusinessDayConvention paymentConvention_;

      // results
      private double? fairRate_;
      private double? fairSpread_;

      // constructor
      public FixedLeg(Type type, double nominal,
                         Schedule fixedSchedule, double fixedRate, DayCounter fixedDayCount,                     
                         BusinessDayConvention? paymentConvention = null) :
         base(1)
      {
         type_ = type;
         nominal_ = nominal;
         fixedSchedule_ = fixedSchedule;
         fixedRate_ = fixedRate;
         fixedDayCount_ = fixedDayCount;

         if (paymentConvention.HasValue)
            paymentConvention_ = paymentConvention.Value;

         legs_[0] = new FixedRateLeg(fixedSchedule)
                                     .withCouponRates(fixedRate, fixedDayCount)
                                     .withPaymentAdjustment(paymentConvention_)
                                     .withNotionals(nominal);


         foreach (var cf in legs_[0])
            cf.registerWith(update);

         switch (type_)
         {
            case Type.Payer:
               payer_[0] = -1.0;
               break;
            case Type.Receiver:
               payer_[0] = +1.0;
               break;
            default:
               throw new ApplicationException("Unknown fixlegs-swap type");
         }
      }

      public override void setupArguments(IPricingEngineArguments args)
      {
         base.setupArguments(args);

         FixedLeg.Arguments arguments = args as FixedLeg.Arguments;
         if (arguments == null)  // it's a swap engine...
            return;

         arguments.type = type_;
         arguments.nominal = nominal_;

         List<CashFlow> fixedCoupons = fixedLeg();

         arguments.fixedResetDates = new InitializedList<Date>(fixedCoupons.Count);
         arguments.fixedPayDates = new InitializedList<Date>(fixedCoupons.Count);
         arguments.fixedCoupons = new InitializedList<double>(fixedCoupons.Count);

         for (int i = 0; i < fixedCoupons.Count; ++i)
         {
            FixedRateCoupon coupon = (FixedRateCoupon)fixedCoupons[i];

            arguments.fixedPayDates[i] = coupon.date();
            arguments.fixedResetDates[i] = coupon.accrualStartDate();
            arguments.fixedCoupons[i] = coupon.amount();
         }
      }


      ///////////////////////////////////////////////////
      // results
      public double fairRate()
      {
         calculate();
         if (fairRate_ == null) throw new ArgumentException("result not available");
         return fairRate_.GetValueOrDefault();
      }

      public double fixedLegBPS()
      {
         calculate();
         if (legBPS_[0] == null) throw new ArgumentException("result not available");
         return legBPS_[0].GetValueOrDefault();
      }
      public double fixedLegNPV()
      {
         calculate();
         if (legNPV_[0] == null) throw new ArgumentException("result not available");
         return legNPV_[0].GetValueOrDefault();
      }

      public double fixedRate { get { return fixedRate_; } }
      public double nominal { get { return nominal_; } }
      public Type swapType { get { return type_; } }
      public List<CashFlow> fixedLeg() { return legs_[0]; }


      protected override void setupExpired()
      {
         base.setupExpired();
         legBPS_[0] = 0.0;
         fairRate_ = fairSpread_ = null;
      }

      public override void fetchResults(IPricingEngineResults r)
      {
         const double basisPoint = 1.0e-4;

         base.fetchResults(r);
         VanillaSwap.Results results = r as VanillaSwap.Results;

         if (results != null)
         { // might be a swap engine, so no error is thrown
            fairRate_ = results.fairRate;
            fairSpread_ = results.fairSpread;
         }
         else
         {
            fairRate_ = null;
            fairSpread_ = null;
         }

         if (fairRate_ == null)
         {
            // calculate it from other results
            if (legBPS_[0] != null)
               fairRate_ = fixedRate_ - NPV_.GetValueOrDefault() / (legBPS_[0] / basisPoint);
         }
      }


      //! %Arguments for simple swap calculation
      new public class Arguments : Swap.Arguments
      {
         public Type type;
         public double nominal;

         public List<Date> fixedResetDates;
         public List<Date> fixedPayDates;
         public List<double> fixedCoupons;


         public Arguments()
         {
            type = Type.Receiver;
            nominal = default(double);
         }

         public override void validate()
         {
            base.validate();

            if (nominal == default(double)) throw new ArgumentException("nominal null or not set");
            if (fixedResetDates.Count != fixedPayDates.Count)
               throw new ArgumentException("number of fixed start dates different from number of fixed payment dates");
            if (fixedPayDates.Count != fixedCoupons.Count)
               throw new ArgumentException("number of fixed payment dates different from number of fixed coupon amounts");
         }
      }
   }
}
